/**
 * Aptana Studio
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license-epl.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.editor.decorator;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;

import com.aptana.core.logging.IdeLog;
import com.aptana.editor.epl.EditorEplPlugin;

/**
 * A lightweight label decorator that looks into IResource markers and decorate with errors or warnings.
 */
public class ProblemsLabelDecorator implements ILightweightLabelDecorator
{

	private static final int ERROR = IMarker.SEVERITY_ERROR;
	private static final int WARNING = IMarker.SEVERITY_WARNING;
	private ListenerList fListeners;
	private IProblemChangedListener fProblemChangedListener;

	/**
	 * This is a special <code>LabelProviderChangedEvent</code> carrying additional information whether the event
	 * origins from a maker change.
	 * <p>
	 * <code>ProblemsLabelChangedEvent</code>s are only generated by <code>
	 * ProblemsLabelDecorator</code>s.
	 * </p>
	 */
	public static class ProblemsLabelChangedEvent extends LabelProviderChangedEvent
	{

		private static final long serialVersionUID = 1L;

		private boolean fMarkerChange;

		/**
		 * @param eventSource
		 *            the base label provider
		 * @param changedResource
		 *            the changed resources
		 * @param isMarkerChange
		 *            <code>true</code> if the change is a marker change; otherwise <code>false</code>
		 */
		public ProblemsLabelChangedEvent(IBaseLabelProvider eventSource, IResource[] changedResource,
				boolean isMarkerChange)
		{
			super(eventSource, changedResource);
			fMarkerChange = isMarkerChange;
		}

		/**
		 * Returns whether this event origins from marker changes. If <code>false</code> an annotation model change is
		 * the origin. In this case viewers not displaying working copies can ignore these events.
		 * 
		 * @return if this event origins from a marker change.
		 */
		public boolean isMarkerChange()
		{
			return fMarkerChange;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see IBaseLabelProvider#dispose()
	 */
	public void dispose()
	{
		if (fProblemChangedListener != null)
		{
			EditorEplPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener);
			fProblemChangedListener = null;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, java.lang.String)
	 */
	public boolean isLabelProperty(Object element, String property)
	{
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see IBaseLabelProvider#addListener(ILabelProviderListener)
	 */
	public void addListener(ILabelProviderListener listener)
	{
		if (fListeners == null)
		{
			fListeners = new ListenerList();
		}
		fListeners.add(listener);
		if (fProblemChangedListener == null)
		{
			fProblemChangedListener = new IProblemChangedListener()
			{
				public void problemsChanged(IResource[] changedResources, boolean isMarkerChange)
				{
					fireProblemsChanged(changedResources, isMarkerChange);
				}
			};
			EditorEplPlugin.getDefault().getProblemMarkerManager().addListener(fProblemChangedListener);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see IBaseLabelProvider#removeListener(ILabelProviderListener)
	 */
	public void removeListener(ILabelProviderListener listener)
	{
		if (fListeners != null)
		{
			fListeners.remove(listener);
			if (fListeners.isEmpty() && fProblemChangedListener != null)
			{
				EditorEplPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener);
				fProblemChangedListener = null;
			}
		}
	}

	private void fireProblemsChanged(IResource[] changedResources, boolean isMarkerChange)
	{
		if (fListeners != null && !fListeners.isEmpty())
		{
			LabelProviderChangedEvent event = new ProblemsLabelChangedEvent(this, changedResources, isMarkerChange);
			Object[] listeners = fListeners.getListeners();
			for (int i = 0; i < listeners.length; i++)
			{
				((ILabelProviderListener) listeners[i]).labelProviderChanged(event);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object,
	 * org.eclipse.jface.viewers.IDecoration)
	 */
	public void decorate(Object element, IDecoration decoration)
	{
		int adornmentFlags = computeAdornmentFlags(element);
		switch (adornmentFlags)
		{
			case ERROR:
				decoration.addOverlay(EditorPluginImages.DESC_OVR_ERROR);
				break;
			case WARNING:
				decoration.addOverlay(EditorPluginImages.DESC_OVR_WARNING);
				break;
		}
	}

	/**
	 * Compute the flags that were set on the give object. We expect an IResource for this computation, and we return
	 * the flags according to the warnings or errors set on the resource.
	 * 
	 * @param obj
	 *            A IResource (expected)
	 * @return An integer representing an ERROR or a WARNING; -1 if the give object was not an IResource or when we got
	 *         an exception retrieving the {@link IMarker}s from the resource.
	 */
	protected int computeAdornmentFlags(Object obj)
	{
		try
		{
			if (obj instanceof IResource)
			{
				return getErrorTicksFromMarkers((IResource) obj, IResource.DEPTH_INFINITE);
			}
		}
		catch (CoreException e)
		{
			if (e.getStatus().getCode() == IResourceStatus.MARKER_NOT_FOUND)
			{
				return -1;
			}
			IdeLog.logWarning(EditorEplPlugin.getDefault(),
					"Error computing label-decoration adornment flags", e, EditorEplPlugin.DEBUG_SCOPE); //$NON-NLS-1$
		}
		return -1;
	}

	private int getErrorTicksFromMarkers(IResource res, int depth) throws CoreException
	{
		if (res == null || !res.isAccessible())
		{
			return -1;
		}
		int info = -1;

		IMarker[] markers = res.findMarkers(IMarker.PROBLEM, true, depth);
		if (markers != null)
		{
			for (int i = 0; i < markers.length && info != ERROR; i++)
			{
				IMarker curr = markers[i];
				int severity = curr.getAttribute(IMarker.SEVERITY, -1);
				if (severity == IMarker.SEVERITY_WARNING)
				{
					info = WARNING;
				}
				else if (severity == IMarker.SEVERITY_ERROR)
				{
					info = ERROR;
				}
			}
		}
		return info;
	}
}
